Update plugin ZTools 提供商 v1.0.1#306
Conversation
- feat: ZTools OCR + 翻译提供商插件(截图识别 / 代码翻译 / manage) - chore: 调整插件命名空间 - chore: 调整资源url - feat: 微信 OCR 原生模块支持 macOS(libwxocr.dylib) - feat: 截图识别结果改为独立窗口展示(左图右文 + 拖动缩放)
There was a problem hiding this comment.
Code Review
This pull request refactors the screenshot OCR feature to display results in a dedicated, borderless sub-window instead of inline. It introduces logo-loading helpers, updates Vite to support a multi-page build, and implements interactive features like image dragging, zooming, and bidirectional highlighting. The review feedback highlights several opportunities to improve robustness, such as adding null checks for display and window instances, handling asynchronous clipboard write errors, implementing an image load error handler, and resetting interaction states when a new image is loaded.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const display = window.ztools.getPrimaryDisplay() | ||
| // workArea 同为 DIP,含任务栏扣除,比 size 更安全 | ||
| const workArea = display.workArea | ||
| const screenWDip = workArea.width | ||
| const screenHDip = workArea.height | ||
| // scaleFactor:如 1 / 1.25 / 1.5 / 1.9 / 2 | ||
| const scaleFactor = display.scaleFactor || 1 |
There was a problem hiding this comment.
在获取屏幕显示信息时,window.ztools.getPrimaryDisplay() 可能会在某些异常情况下(例如多显示器切换、系统休眠唤醒或 API 未完全加载时)返回 null 或 undefined。为了提高代码的健壮性,建议对 display 进行空值保护,避免因直接读取 display.workArea 而导致程序崩溃。
const display = window.ztools.getPrimaryDisplay()
// workArea 同为 DIP,含任务栏扣除,比 size 更安全
const workArea = display?.workArea || { width: 1280, height: 800, x: 0, y: 0 }
const screenWDip = workArea.width
const screenHDip = workArea.height
// scaleFactor:如 1 / 1.25 / 1.5 / 1.9 / 2
const scaleFactor = display?.scaleFactor || 1
| function injectData( | ||
| win: BrowserWindow.WindowInstance, | ||
| data: { image: string; lines: OcrLine[]; isDark: boolean; logo?: string } | ||
| ): void { | ||
| try { | ||
| // 序列化为安全的 JSON,避免引号/换行破坏 JS 字符串 | ||
| const payload = JSON.stringify(data) | ||
| const code = `window.__loadScreenOcrResult && window.__loadScreenOcrResult(${payload});` | ||
| win.webContents.executeJavaScript(code) |
There was a problem hiding this comment.
在 injectData 函数中,如果传入的 win 实例为 null 或 undefined(例如创建窗口失败时),直接访问 win.webContents 会抛出 TypeError。虽然外层有 try-catch 保护,但在函数开头增加显式的空值校验是更优雅且安全的防御性编程实践。
function injectData(
win: BrowserWindow.WindowInstance,
data: { image: string; lines: OcrLine[]; isDark: boolean; logo?: string }
): void {
if (!win) return
try {
// 序列化为安全的 JSON,避免引号/换行破坏 JS 字符串
const payload = JSON.stringify(data)
const code = 'window.__loadScreenOcrResult && window.__loadScreenOcrResult(' + payload + ');'
win.webContents.executeJavaScript(code)
| function copyText(text: string) { | ||
| if (!text) return | ||
| try { | ||
| navigator.clipboard?.writeText(text) | ||
| } catch (_) { | ||
| /* 子窗口可能无 clipboard 权限,静默失败 */ | ||
| } | ||
| copied.value = text | ||
| if (copiedTimer) window.clearTimeout(copiedTimer) | ||
| copiedTimer = window.setTimeout(() => (copied.value = ''), 1200) | ||
| } |
There was a problem hiding this comment.
navigator.clipboard.writeText 是一个异步操作,返回一个 Promise。在当前的 try-catch 块中,由于没有使用 await,如果 writeText 发生错误(例如用户未授予剪贴板权限),该错误将无法被同步的 try-catch 捕获,从而导致未捕获的 Promise 拒绝(Unhandled Promise Rejection)。建议将 copyText 改为 async 函数并使用 await。
async function copyText(text: string) {
if (!text) return
try {
await navigator.clipboard?.writeText(text)
} catch (_)
/* 子窗口可能无 clipboard 权限,静默失败 */
}
copied.value = text
if (copiedTimer) window.clearTimeout(copiedTimer)
copiedTimer = window.setTimeout(() => (copied.value = ''), 1200)
}
| function loadImage() { | ||
| if (!image.value) return | ||
| const img = new Image() | ||
| img.onload = () => { | ||
| naturalWidth.value = img.naturalWidth | ||
| naturalHeight.value = img.naturalHeight | ||
| lastImg = img | ||
| nextTick(() => { | ||
| drawToCanvas(img) | ||
| // 初次加载按「适应窗口」展示(不一定是 1:1),避免大图溢出 | ||
| fitView() | ||
| }) | ||
| } | ||
| img.src = image.value | ||
| } |
There was a problem hiding this comment.
在 loadImage 中,如果图片加载失败(例如 base64 数据损坏或格式不支持),img.onload 将不会触发,且没有任何错误处理。这会导致界面一直处于加载状态(loading 为 true)。建议为 img 添加 onerror 事件处理函数,以便在加载失败时更新状态并提示用户。
function loadImage() {
if (!image.value) return
const img = new Image()
img.onload = () => {
naturalWidth.value = img.naturalWidth
naturalHeight.value = img.naturalHeight
lastImg = img
nextTick(() => {
drawToCanvas(img)
// 初次加载按「适应窗口」展示(不一定是 1:1),避免大图溢出
fitView()
})
}
img.onerror = () => {
loading.value = false
errorText.value = '图片加载失败'
}
img.src = image.value
}
| watch(image, (src) => { | ||
| if (src) loadImage() | ||
| else { | ||
| naturalWidth.value = 0 | ||
| naturalHeight.value = 0 | ||
| lastImg = null | ||
| } | ||
| }) |
There was a problem hiding this comment.
当 image 发生变化(例如用户重新截图并加载新结果)时,应该重置上一次截图的交互状态(如 activeIndex、hoveredIndex 和 flashedIds)。否则,新图片加载后可能会残留旧的高亮状态或导致索引越界。
watch(image, (src) => {
activeIndex.value = null
hoveredIndex.value = null
flashedIds.value = []
if (src) loadImage()
else {
naturalWidth.value = 0
naturalHeight.value = 0
lastImg = null
}
})
插件信息
本次变更
截图 / 演示
自检清单
plugins/f-provider/目录此 PR 由 ztools-plugin-cli 自动管理:每次
ztools publish在分支上追加一个 commit,PR 链接保持不变。